//************************************************************************************************************************
// Sketch name   : ODPLC
// Version       : 0.3.0
// Author        : astroz78 & osmosis
// Creation date : 10-04-2010
// Release note  : 10-04-2010 0.1.0: Prima versione
//                 09-07-2010 0.3.0: Rivisto protocollo e gestione comunicazione LAN
//************************************************************************************************************************
#include <Ethernet.h>
#include <EEPROM.h>

//-------------------------------//
// Ethernet shield configuration //
//-------------------------------//
const  uint16_t        TCP_PORT = 6301;
       byte            macAddress[6];
       byte            ipAddress[4];
       uint8_t         nodeID;
       Server          server(TCP_PORT);
       unsigned long   socketTimeout = 0;
const  unsigned long   SOCKET_TIMEOUT = 10000;
                       
       char            buffer = 0;        //Singola occorrenza scaricata dal buffer ethernet
       boolean         escape = false;    //Modalità escape
const  char            ESCAPE_CODE  = B01111101;
const  char            EXECUTE      = B01111111;
const  char            CLOSE_SOCKET = B01111110;

       uint8_t         cmd[100];          //Buffer comando
       uint8_t         iCmd=0;            //Indice array bufffer comando


//-----------------------------//
// Arduino ports configuration //
//-----------------------------//


//Shift-register Output
const  uint8_t         PORT_SHIFTOUT_LATCH = 7;
const  uint8_t         PORT_SHIFTOUT_CLOCK = 8;
const  uint8_t         PORT_SHIFTOUT_DATA  = 9;
       int             output;

//Shift-register Input


//-----------//
// S E T U P //
//-----------//
void setup() {
  Serial.begin(9600);
  Serial.println("STARTUP");

  //Inizializza lo shift register
  output=0;
  pinMode(PORT_SHIFTOUT_LATCH, OUTPUT);
  pinMode(PORT_SHIFTOUT_CLOCK, OUTPUT);
  pinMode(PORT_SHIFTOUT_DATA, OUTPUT);
  sendData(output);
  
  //Lettura configurazione ed inizializzazione scheda di rete
  ethernetInit();
  
  //Lettura configurazione porte di Input
  //inputPortConfig();
}


//-------------------//
// S U P E R L O O P //
//-------------------//
void loop() {
  //Scansiona gli input locali (pulsanti)
  localService();
  
  //Verifica se c'è un comando "ritardato" da gestire
  timerService();
  
  //Verifica se ci sono dati input dal canale Ethernet
  remoteService();
}


void timerService() {
}


void localService() {
}


void remoteService() {
  Client client = server.available();
  if (client) {
  
    while (client.connected()) {
      if (client.available()) {
        socketTimeout = millis() + SOCKET_TIMEOUT;
      
        buffer = client.read();
        //Serial.print(millis());
        //Serial.print(" ricevuto: ");
        //Serial.println((int)buffer);
        
        if (!escape && buffer == ESCAPE_CODE)  {
          escape = true;
        } else {
          if (!escape && buffer == EXECUTE) {
            executeCmd(client); 
            iCmd = 0;
          } else if (!escape && buffer == CLOSE_SOCKET) {
            break;
          } else if (buffer != -1) {
            cmd[iCmd++] = buffer;
            escape = false;
          }
        }
      }
      
      if (socketTimeout < millis()) break;
    }
    
    client.flush();
    client.stop();
    socketTimeout = 0;
    
    //Serial.print(millis());
    //Serial.println(" fine");
  }
}

//---------------------//
// P R O T O C O L L O //
//---------------------//
void executeCmd(Client client) {
  if (iCmd == 0) return;
  
  //Debug
  Serial.print("*** ");
  Serial.print(millis());
  Serial.print(" - cmdBuffer[");
  for (int j=0; j<iCmd; j++) {
    Serial.print((int)cmd[j]);
    if (j!=iCmd-1) Serial.print("-");
  }
  Serial.print("] ");
  
  uint8_t    destNodeID = 0;

  //Determinazione destinatario comando
  if (bitRead(cmd[0], 7)==1) {
    destNodeID = (cmd[0] & B01111100) >> 2;
    cmd[0] = cmd[0] & B10000011;
  } else if (cmd[0] == B00000010) {
    destNodeID = cmd[1];
    for (int j=2; j<iCmd; j++) {
      cmd[j-2]=cmd[j];
      iCmd-=2;
    }
  }
  
  Serial.print(" destNodeID[");
  Serial.print((int)destNodeID);
  Serial.print("] ");
  
  if (destNodeID != 0 && destNodeID != nodeID) {  //Se il comando è remoto allora lo invia al destinatario
    sendCmd(destNodeID, client);
  } else {                                        //Se il comando è locale allora lo esegue
    switch (cmd[0]) {
      case B01000000:         //configWrite
        configWrite(client);
        break;
      case B01000001:          //configRead
        configRead(client);
        break;
      case B01000010:          //configApply
        configApply(client);
        break;
      case B01000011:         //ping
        ping(client);
        break;
      case B01000100:         //getPort
        getPort(client);
        break;
      case B01000101:         //getInfo
        getInfo(client);
        break;
      case B10000000:         //Inverte lo stato di una porta
      case B10000010:         //Imposta lo stato di una porta
      case B10000011:         //Imposta lo stato di una porta
        Serial.print("setportstatus");
        uint8_t port = cmd[1];
        uint8_t newStatus = 2;
        if (bitRead(cmd[0], 1)==1) newStatus = bitRead(cmd[0], 0);
        setPortStatus(port, newStatus, client);
        break;
    }
  }

  Serial.println("-END");
}


void sendCmd(uint8_t destNodeID, Client client) {
  
}


//---------//
// C O R E //
//---------//
void setPortStatus(uint8_t port, uint8_t status, Client client) {
  uint8_t newStatus;
  if (status==2) {         //2=inverte lo stato
    if (bitRead(output, port) == 0) {
      newStatus=1;
    } else {
      newStatus=0;
    }
  } else {
    newStatus=status;
  }
  bitWrite(output, port, newStatus);
  sendData(output);
  
  //writePortStatus(port, client);
}


void configWrite(Client client) {
  Serial.print("- configWrite");
  
  uint16_t address = createInt(cmd[1], cmd[2]);
  uint8_t  value   = cmd[3];
  
  EEPROM.write(address, value);

  Serial.print(" address= ");
  Serial.print(address);
  Serial.print(" value= ");
  Serial.print((int)value);
}


void configRead(Client client) {
  Serial.print("- configRead");
  
  uint16_t address = createInt(cmd[1], cmd[2]);
  uint8_t  value;
  
  value = EEPROM.read(address);
  
  server.println(value);

  Serial.print("address= ");
  Serial.print(address);
  Serial.print(" value= ");
  Serial.print((int)value);
}


void configApply(Client client) {
  Serial.println("- configApply");
  
  ethernetInit();
}


void ping(Client client) {
  Serial.print("- ping");
  
  client.write("OK");
}


void getPort(Client client) {
  Serial.print("- getPort");

  writePortStatus(cmd[1], client);
}


void getInfo(Client client) {
  Serial.print("- getInfo");

  uint8_t reg = cmd[1];
}


void writePortStatus(int port, Client client) {
  if (bitRead(output, port) == 0) {
    client.write("0");
  } else {
    client.write("1");
  }
}


void ethernetInit() {
  //Carica la prima cifra dell'indirizzo IP dalla EEPROM e proprio
  //tramite questa che stabilisce se la EEPROM e stata programmata
  ipAddress[0]=EEPROM.read(0);

  //Se la 1ma cifra dell'IP non e valida vuol dire che la
  //EEPROM non e programmata e quindi imposto i valori di default
  if (ipAddress[0]!=10 && ipAddress[0]!=172 && ipAddress[0]!=192) {
    //Ip di default 192.168.0.233
    ipAddress[0]=192;
    ipAddress[1]=168;
    ipAddress[2]=0;
    nodeID=33;
    ipAddress[3]=200 + nodeID;
    
    //MAC address di default DE:AD:BE:EF:FE:ED
    macAddress[0]=0xDE;
    macAddress[1]=0xAD;
    macAddress[2]=0xBE;
    macAddress[3]=0xEF;
    macAddress[4]=0xFE;
    macAddress[5]=0xED;
  } else {
    //Finisce di caricare l'indirizzo IP dalla EEPROM
    ipAddress[1]=EEPROM.read(1);
    ipAddress[2]=EEPROM.read(2);
    nodeID=EEPROM.read(4);
    ipAddress[3]=EEPROM.read(3) + nodeID;

    //Carica il MAC address dalla configurazione
    macAddress[0]=EEPROM.read( 5);
    macAddress[1]=EEPROM.read( 6);
    macAddress[2]=EEPROM.read( 7);
    macAddress[3]=EEPROM.read( 8);
    macAddress[4]=EEPROM.read( 9);
    macAddress[5]=EEPROM.read(10);
  }

  //Inizializza la Ethernet
  Ethernet.begin(macAddress, ipAddress);
  server.begin();

  //Stampa l'IP configurato
  Serial.print  ("IP  : ");
  Serial.print  ((int)ipAddress[0]);
  Serial.print  (".");
  Serial.print  ((int)ipAddress[1]);
  Serial.print  (".");
  Serial.print  ((int)ipAddress[2]);
  Serial.print  (".");
  Serial.println((int)ipAddress[3]);
  Serial.print  ("NODE: ");
  Serial.println((int)nodeID);
}


void sendData(int data) {
  digitalWrite(PORT_SHIFTOUT_LATCH, LOW);
  shiftOut(PORT_SHIFTOUT_DATA, PORT_SHIFTOUT_CLOCK, LSBFIRST, data&255);   
  shiftOut(PORT_SHIFTOUT_DATA, PORT_SHIFTOUT_CLOCK, LSBFIRST, data>>8);   
  digitalWrite(PORT_SHIFTOUT_LATCH, HIGH); 
}


//-----------//
// U T I L S //
//-----------//
uint16_t createInt(uint8_t b1, uint8_t b2) {
  uint16_t ret = 0;
  ret =  b1 << 8;
  ret += b2;
  return ret;
}
